package ru.susu.algebra.centralunits.alternating.period;

import java.math.BigInteger;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.ObjectUtils;
import org.apache.log4j.Logger;

import com.google.common.collect.Lists;

import ru.susu.algebra.centralunits.alternating.initializers.LocalUnitsInitializer;
import ru.susu.algebra.centralunits.alternating.initializers.QuadraticFieldsInitializer;
import ru.susu.algebra.field.quadratic.GeneralUnitRecursivePeriodExtractor;
import ru.susu.algebra.field.quadratic.QuadraticFieldGeneralForm;
import ru.susu.algebra.field.quadratic.QuadraticInteger;
import ru.susu.algebra.pair.Pair;
import ru.susu.algebra.partition.Partition;
import ru.susu.algebra.properties.IPropertySource;

/**
 * Реализация {@link IAlternatingPeriodExtractor}, основанная на полном представлении периода и просмотре всего списка значений
 * Подходит для небольших значений периодов
 *
 * @author akargapolov
 * @since: 14.10.2010
 */
public class PlainAlternatingPeriodExtractor implements IAlternatingPeriodExtractor
{
	private static final Logger _log = Logger.getLogger("PlainAlternatingPeriodExtractor");

	/**
	 * {@inheritDoc}
	 */
	@Override
	public BigInteger getIntegerPeriod(IPropertySource ps, Partition partition, BigInteger mod)
	{
		return BigInteger.valueOf(getPeriod(ps, partition, mod).size() - 2);
	}

	private static final String PERIOD = PlainAlternatingPeriodExtractor.class.getSimpleName() + "_Period";

	/**
	 * {@inheritDoc}
	 */
	@Override
	public List<Pair<BigInteger, BigInteger>> getPeriod(IPropertySource ps, Partition partition, BigInteger mod)
	{
		if (!ps.containsKey(PERIOD))
		{
			ps.setValue(PERIOD, new HashMap<Pair<Partition, BigInteger>, List<Pair<BigInteger, BigInteger>>>());
		}
		@SuppressWarnings("unchecked")
		Map<Pair<Partition, BigInteger>, List<Pair<BigInteger, BigInteger>>> map = (Map<Pair<Partition, BigInteger>, List<Pair<BigInteger, BigInteger>>>) ps.getValue(PERIOD);
		Pair<Partition, BigInteger> key = Pair.pair(partition, mod);
		if (!map.containsKey(key))
		{
			QuadraticFieldGeneralForm generateUnit = QuadraticFieldsInitializer.getGeneralUnit(ps, partition);
			List<Pair<BigInteger, BigInteger>> list = GeneralUnitRecursivePeriodExtractor.instance.performOperation(generateUnit, mod);
			map.put(key, list);
		}
		return map.get(key);
	}

	private static final String GOOD_PERIODS = PlainAlternatingPeriodExtractor.class.getSimpleName() + "_GoodPeriods";
	/**
	 * {@inheritDoc}
	 */
	@Override
	public List<Pair<BigInteger, BigInteger>> listGoodPeriods(IPropertySource ps, Partition partition, BigInteger mod)
	{
		if (!ps.containsKey(GOOD_PERIODS))
		{
			ps.setValue(GOOD_PERIODS, new HashMap<Pair<Partition, BigInteger>, List<Pair<BigInteger, BigInteger>>>());
		}
		@SuppressWarnings("unchecked")
		Map<Pair<Partition, BigInteger>, List<Pair<BigInteger, BigInteger>>> map = (Map<Pair<Partition, BigInteger>, List<Pair<BigInteger, BigInteger>>>) ps
				.getValue(GOOD_PERIODS);
		Pair<Partition, BigInteger> key = Pair.pair(partition, mod);
		if (!map.containsKey(key))
		{
			Date start = new Date();
			_log.info("start - listGoodPeriods");
			BigInteger alphaC = LocalUnitsInitializer.getAlphaCoefficient(ps, partition).mod(mod);
			BigInteger betaC = LocalUnitsInitializer.getBetaCoefficient(ps, partition).mod(mod);
			List<Pair<BigInteger, BigInteger>> period = LocalUnitsInitializer.getPeriod(ps, partition, mod);
			BigInteger intPeriod = LocalUnitsInitializer.getIntegerPeriod(ps, partition, mod);

			List<Pair<BigInteger, BigInteger>> list = Lists.newArrayList();
			QuadraticInteger character = QuadraticFieldsInitializer.getCharacter(ps, partition);
			int bd = character.getB().multiply(character.getD()).intValue();

			for (int index = 0; index < intPeriod.intValue(); index++)
			{
				BigInteger alpha = period.get(index).getKey();
				BigInteger beta = period.get(index).getValue();
				Pair<BigInteger, BigInteger> t = calcT(alphaC, betaC, alpha, beta, mod, BigInteger.valueOf(index), bd);
				if (t != null)
				{
					list.add(t);
				}
			}
			map.put(key, list);
			_log.info("listGoodPeriods time - " + (new Date().getTime() - start.getTime()) + "ms");
		}
		return map.get(Pair.pair(partition, mod));
	}

	public static Pair<BigInteger, BigInteger> calcT(BigInteger alphaC, BigInteger betaC, BigInteger alpha, BigInteger beta, BigInteger mod, BigInteger index, int bd)
	{
		for (int t = 0; t < bd; t++)	//примиивный поиск t перебором
		{
			BigInteger talpha = BigInteger.ONE.add(alphaC.multiply(BigInteger.valueOf(t))).mod(mod);
			BigInteger tbeta = betaC.multiply(BigInteger.valueOf(t)).mod(mod);
			if (ObjectUtils.equals(alpha, talpha) && ObjectUtils.equals(beta, tbeta))
			{
				return Pair.pair(index, BigInteger.valueOf(t));
			}
		}
		return null;
	}
}
